package templates

import (
	"context"
	"fmt"
	"github.com/Masterminds/sprig/v3"
	"github.com/gohobby/deepcopy"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"sigs.k8s.io/yaml"
	"strings"
	"text/template"
)

func LoadTemplate(ctx context.Context, ts ...v1alpha1.Template) *template.Template {
	templateEngine := template.New("common").
		Delims("{{%", "%}}").
		Funcs(GomplateFuncs).
		Funcs(sprig.TxtFuncMap())
	for _, t := range ts {
		_, err := templateEngine.New(t.Name).Parse(t.Spec.Data)
		if err != nil {
			logs.Errorf("LoadTemplate failed %s", err)
			return nil
		}
	}
	return templateEngine
}

var GomplateFuncs = template.FuncMap{
	"del":                   Del,
	"toYaml":                ToYAML,
	"indent":                Indent,
	"nindent":               Nindent,
	"fromYaml":              FromYAML,
	"replaceKey":            ReplaceKey,
	"RBACconfigPatchesToV3": ConfigPatchesRBACToV3,
}

// FromYAML decodes a YAML string into an interface{}. This allows injection templates to access
// configs defined as YAML strings.
func FromYAML(in string) (interface{}, error) {
	var out interface{}

	if err := yaml.Unmarshal([]byte(in), &out); err != nil {
		return nil, fmt.Errorf("failed to unmarshal yaml with error: %v. source: %q", err, in)
	}
	return out, nil
}

// ToYAML encodes an interface{} into a YAML string.
func ToYAML(in interface{}) (string, error) {
	out, err := yaml.Marshal(in)
	if err != nil {
		return "", fmt.Errorf("failed to unmarshal yaml with error: %v. source: %q", err, out)
	}
	if in == nil {
		out = []byte{}
	}

	return string(out), nil
}

func Indent(in string, spacesNum int) string {
	spaces := strings.Repeat(" ", spacesNum)
	out := strings.Replace(in, "\n", "\n"+spaces, -1) // nolint:gocritic
	return out + "\n"
}

func Nindent(in string, spacesNum int) string {
	out := "\n" + in
	return Indent(out, spacesNum)
}

func ReplaceKey(oldK, newKey string, in any) any {
	oldKeyPath := strings.Split(oldK, ".")
	tmp := in

	for i, key := range oldKeyPath {
		tmpMap, ok := tmp.(map[string]any)
		if !ok {
			logs.Debugf("ReplaceKey: can not assign to map %v", in)
			return in
		}

		tmp = tmpMap[key]

		if i == len(oldKeyPath)-1 {
			v, ok := tmpMap[key]
			if !ok {
				return in
			}

			delete(tmpMap, key)
			tmpMap[newKey] = v
			logs.Debugf("ReplaceKey executed: %v -> %v", key, newKey)
		}
	}

	return in
}

// Del go template func. Deleting key from map
func Del(path string, obj any) any {
	pathToDelete := strings.Split(path, ".")
	tmp := obj

	for i, key := range pathToDelete {
		tmpMap, ok := tmp.(map[string]any)
		if !ok {
			logs.Debugf("Del: can not assign to map %v", obj)
			return obj
		}

		tmp = tmpMap[key]

		if i == len(pathToDelete)-1 {
			_, ok := tmpMap[key]
			if !ok {
				logs.Debugf("Del: key was not deleted")
				return obj
			}
			delete(tmpMap, key)
			logs.Debugf("Del: key %v was deleted", key)
		}
	}

	return obj
}

func ConfigPatchesRBACToV3(in interface{}) (interface{}, error) {
	copy := deepcopy.DeepCopy(in)
	copy, err := replaceFilterChainName(copy)
	if err != nil {
		return ToYAML(in)
	}
	copy, err = replaceRBACTypedConfig(copy, "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC")
	if err != nil {
		return ToYAML(in)
	}

	return ToYAML(copy)
}

func replaceRBACTypedConfig(in interface{}, filterType string) (interface{}, error) {
	_, ok := in.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("cant get value from yaml, check syntax")
	}
	patch, ok := in.(map[string]interface{})["patch"]
	if !ok {
		return nil, fmt.Errorf("cant get patch field")
	}
	_, ok = patch.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("patch is not a map check syntax")
	}
	value, ok := patch.(map[string]interface{})["value"]
	if !ok {
		return nil, fmt.Errorf("cant get patch.value field")
	}
	_, ok = value.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("value is not a map check syntax")
	}
	config, ok := value.(map[string]interface{})["config"]
	if !ok {
		return nil, fmt.Errorf("cant get patch.value.config field")
	}
	value.(map[string]interface{})["typed_config"] = config
	value.(map[string]interface{})["typed_config"].(map[string]interface{})["@type"] = filterType
	delete(value.(map[string]interface{}), "config")

	typedConfig := value.(map[string]interface{})["typed_config"]

	rules, ok := typedConfig.(map[string]interface{})["rules"]
	if !ok {
		return nil, fmt.Errorf("cant get patch.value.config.rules field")
	}
	if _, ok = rules.(map[string]interface{}); !ok {
		return nil, fmt.Errorf("rules is not a map, check syntax")
	}
	policies, ok := rules.(map[string]interface{})["policies"]
	if !ok {
		return nil, fmt.Errorf("cant get patch.value.config.rules.policies field")
	}
	if _, ok = policies.(map[string]interface{}); !ok {
		return nil, fmt.Errorf("policies is not a map, check syntax")
	}
	for k, v := range policies.(map[string]interface{}) {
		if _, ok = v.(map[string]interface{}); !ok {
			return nil, fmt.Errorf("%v is not a map, check syntax", k)
		}
		permissions, ok := v.(map[string]interface{})["permissions"]
		if !ok {
			return nil, fmt.Errorf("cant get patch.value.config.rules.policies.%v.permissions field", k)
		}
		if _, ok = permissions.([]interface{}); !ok {
			if _, ok = permissions.(map[string]interface{}); !ok {
				return nil, fmt.Errorf("permissions is not a map, check syntax")
			}
			var perms []interface{}
			for perm_key, perm_value := range permissions.(map[string]interface{}) {
				permMap := make(map[string]interface{})
				permMap[perm_key] = perm_value
				perms = append(perms, permMap)
			}
			policies.(map[string]interface{})[k].(map[string]interface{})["permissions"] = perms
		}
		principals, ok := v.(map[string]interface{})["principals"]
		if !ok {
			return nil, fmt.Errorf("cant get patch.value.config.rules.policies.%v.principals field", k)
		}
		if _, ok = principals.([]interface{}); !ok {
			if _, ok = principals.(map[string]interface{}); !ok {
				return nil, fmt.Errorf("principals is not a map, check syntax")
			}
			var princ []interface{}
			for pr_key, pr_value := range principals.(map[string]interface{}) {
				permMap := make(map[string]interface{})
				permMap[pr_key] = pr_value
				princ = append(princ, permMap)
			}
			policies.(map[string]interface{})[k].(map[string]interface{})["principals"] = princ
		}
	}
	return in, nil

}

func replaceFilterChainName(out interface{}) (interface{}, error) {
	_, ok := out.(map[string]interface{})
	if !ok {
		return out, fmt.Errorf("cant get value from yaml, check syntax")
	}
	match, ok := out.(map[string]interface{})["match"]
	if !ok {
		return out, fmt.Errorf("cant get match field")
	}
	_, ok = match.(map[string]interface{})
	if !ok {
		return out, fmt.Errorf("match is not a map check syntax")
	}
	listener, ok := match.(map[string]interface{})["listener"]
	if !ok {
		return out, fmt.Errorf("cant get match.listener field")
	}
	_, ok = listener.(map[string]interface{})
	if !ok {
		return out, fmt.Errorf("listener is not a map, check syntax")
	}
	filterChain, ok := listener.(map[string]interface{})["filterChain"]
	if !ok {
		return out, fmt.Errorf("cant get match.listener.filterChain field")
	}
	_, ok = filterChain.(map[string]interface{})
	if !ok {
		return out, fmt.Errorf("filterChain is not a map, check syntax")
	}
	filter, ok := filterChain.(map[string]interface{})["filter"]
	if !ok {
		return out, fmt.Errorf("cant get match.listener.filterChain.filter field")
	}
	_, ok = filter.(map[string]interface{})
	if !ok {
		return out, fmt.Errorf("filter is not a map, check syntax")
	}
	_, ok = filter.(map[string]interface{})["name"]
	if !ok {
		return out, fmt.Errorf("cant get match.listener.filterChain.filter.name field")
	}
	filter.(map[string]interface{})["name"] = "envoy.filters.network.http_connection_manager"
	if subfilter, ok := filter.(map[string]interface{})["subFilter"]; ok {
		if _, ok = subfilter.(map[string]interface{}); !ok {
			return out, nil
		}
		name, ok := subfilter.(map[string]interface{})["name"]
		if !ok {
			return out, nil
		}
		if name == "envoy.router" {
			subfilter.(map[string]interface{})["name"] = "envoy.filters.http.router"
		}

		return out, nil
	}
	return out, nil
}
